﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using CTS.Com.Domain.Exceptions;
using CTS.Com.Domain.Helper;
using CTS.Com.Domain.Model;

namespace CTS.Data.Domain.Dao
{
    public class GenericDao<T> where T : ContextBase
    {
        #region Properties

        // Quản lý dữ liệu
        public T EntityManager { get; set; }

        #endregion

        #region Contants

        // Tên field trong database
        private const string DELETE_FLAG = "DeleteFlag";
        private const string LOCALE_CD = "LocaleCd";

        #endregion

        #region Contructors

        /// <summary>
        /// GenericDao
        /// </summary>
        protected GenericDao()
        {
            // Khởi tại lớp quản lý dữ liệu
            EntityManager = DataHelper.CreateObject<T>(
                global::CTS.Data.Properties.Settings.Default.ConnectionString);
            // Trường hợp không tồn tại database
            if (EntityManager.DatabaseExists() == false) {
                throw new ExecuteException("F_MSG_00001");
            }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Lấy đối tượng bằng sql
        /// </summary>
        /// <typeparam name="TObject">Đối tượng trả về</typeparam>
        /// <param name="sql">Chuỗi sql</param>
        /// <param name="param">Tham số xử lý</param>
        /// <returns>Đối tượng (TObject)</returns>
        public TObject GetObjectBySql<TObject>(string sql, object param)
        {
            // Tiến hành thực thi query
            var query = EntityManager.ExecuteQuery<TObject>(sql, param);
            // Kết quả trả về
            return query.SingleOrDefault();
        }

        /// <summary>
        /// Lấy đối tượng bằng file sql
        /// </summary>
        /// <typeparam name="TObject">Đối tượng trả về</typeparam>
        /// <param name="fileName">Tên file sql</param>
        /// <param name="param">Tham số xử lý</param>
        /// <returns>Đối tượng (TObject)</returns>
        public TObject GetObjectByFile<TObject>(string fileName, object param)
        {
            // Lấy loại của đối tượng gọi 
            var type = AppHelper.GetCallerType(new StackFrame(1));
            // Lấy đường dẫn resource theo loại
            var resName = AppHelper.GetResourceByType(type, fileName);
            // Lấy chuỗi sql
            var sql = FileHelper.ToString(type, resName);
            // Kết quả trả về
            return GetObjectBySql<TObject>(sql, param);
        }

        /// <summary>
        /// Lấy danh sách đối tượng bằng sql
        /// </summary>
        /// <typeparam name="TObject">Đối tượng trả về</typeparam>
        /// <param name="sql">Chuỗi sql</param>
        /// <param name="param">Tham số xử lý</param>
        /// <returns>Danh sách đối tượng (TObject)</returns>
        public IList<TObject> GetListBySql<TObject>(string sql, object param)
        {
            // Tiến hành thực thi query
            var query = EntityManager.ExecuteQuery<TObject>(sql, param);
            // Kết quả trả về
            return query.ToList();
        }

        /// <summary>
        /// Lấy danh sách đối tượng bằng file sql
        /// </summary>
        /// <typeparam name="TObject">Đối tượng trả về</typeparam>
        /// <param name="fileName">Tên file sql</param>
        /// <param name="param">Tham số xử lý</param>
        /// <returns>Danh sách đối tượng (TObject)</returns>
        public IList<TObject> GetListByFile<TObject>(string fileName, object param)
        {
            // Lấy loại của đối tượng gọi 
            var type = AppHelper.GetCallerType(new StackFrame(1));
            // Lấy đường dẫn resource theo loại
            var resName = AppHelper.GetResourceByType(type, fileName);
            // Lấy chuỗi sql
            var sql = FileHelper.ToString(type, resName);
            // Kết quả trả về
            return GetListBySql<TObject>(sql, param);
        }

        /// <summary>
        /// Lấy danh sách đối tượng bằng file sql
        /// </summary>
        /// <typeparam name="TObject">Đối tượng trả về</typeparam>
        /// <param name="fileName">Tên file sql</param>
        /// <param name="param">Tham số xử lý</param>
        /// <returns>Danh sách đối tượng (TObject)</returns>
        public PagerInfoModel<TObject> GetPagerByFile<TObject>(string fileName, PagerInfoModel<TObject> param)
        {
            // Khai báo biến cục bộ
            var pagerModel = new PagerInfoModel<TObject>();
            var limit = decimal.ToInt32(param.Limit);
            var offset = decimal.ToInt32(param.Offset);
            // Trương hợp limit là 0
            if (limit <= 0) {
                limit = 20;
            }
            // Lấy loại của đối tượng gọi 
            var type = AppHelper.GetCallerType(new StackFrame(1));
            // Lấy đường dẫn resource theo loại
            var resName = AppHelper.GetResourceByType(type, fileName);
            // Lấy chuỗi sql
            var sql = FileHelper.ToString(type, resName);
            // Lấy dữ liệu
            var listData = GetListBySql<TObject>(sql, param);
            // Gán dữ liệu
            pagerModel.Total = listData.Count;
            pagerModel.ListData = listData.Skip(offset).Take(limit).ToList();
            // Kết quả trả về
            return pagerModel;
        }

        /// <summary>
        /// Xác nhận các thay đổi tại database.
        /// </summary>
        public void SubmitChanges()
        {
            EntityManager.SubmitChanges();
        }

        /// <summary>
        /// Kiểm tra tồn tại (TEntity)
        /// </summary>
        /// <typeparam name="TEntity">Đối tượng entity</typeparam>
        /// <param name="keys">Giá trị các khóa chính</param>
        /// <param name="ignoreDeleteFlag">Bỏ qua cờ xóa</param>
        /// <returns>True/False</returns>
        public bool IsExist<TEntity>(string[] keys, bool ignoreDeleteFlag) where TEntity : class
        {
            // Lấy entity
            var table = EntityManager.GetTable<TEntity>();
            // Lấy biểu thức (SelectSingle)
            var predicate = ExpressionSelectSingle<TEntity>(keys, ignoreDeleteFlag);
            // Lấy count trong entity
            var count = table.LongCount(predicate);
            // Kết quả trả về
            return count >= decimal.One;
        }

        /// <summary>
        /// Kiểm tra tồn tại (TEntity)
        /// </summary>
        /// <typeparam name="TEntity">Đối tượng entity</typeparam>
        /// <param name="keys">Giá trị các khóa chính</param>
        /// <param name="ignoreDeleteFlag">Bỏ qua cờ xóa</param>
        /// <returns>True/False</returns>
        public bool IsExist<TEntity>(string key, bool ignoreDeleteFlag) where TEntity : class
        {
            return IsExist<TEntity>(new string[] { key }, ignoreDeleteFlag);
        }

        /// <summary>
        /// Lấy đối tượng entity (TEntity)
        /// </summary>
        /// <typeparam name="TEntity">Đối tượng entity</typeparam>
        /// <param name="keys">Giá trị các khóa chính</param>
        /// <param name="ignoreDeleteFlag">Bỏ qua cờ xóa</param>
        /// <returns>Đối tượng entity</returns>
        public TEntity GetSingle<TEntity>(string[] keys, bool ignoreDeleteFlag) where TEntity : class
        {
            // Lấy entity
            var table = EntityManager.GetTable<TEntity>();
            // Lấy biểu thức (SelectSingle)
            var predicate = ExpressionSelectSingle<TEntity>(keys, ignoreDeleteFlag);
            // Kết quả trả về
            return table.SingleOrDefault(predicate);
        }

        /// <summary>
        /// Lấy đối tượng entity (TEntity)
        /// </summary>
        /// <typeparam name="TEntity">Đối tượng entity</typeparam>
        /// <param name="keys">Giá trị các khóa chính</param>
        /// <param name="ignoreDeleteFlag">Bỏ qua cờ xóa</param>
        /// <returns>Đối tượng entity</returns>
        public TEntity GetSingle<TEntity>(string key, bool ignoreDeleteFlag) where TEntity : class
        {
            return GetSingle<TEntity>(new string[] { key }, ignoreDeleteFlag);
        }

        /// <summary>
        /// Lấy danh sách đối tượng entity (TEntity)
        /// </summary>
        /// <typeparam name="TEntity">Đối tượng entity</typeparam>
        /// <param name="localeCd">Mã ngôn ngữ</param>
        /// <param name="ignoreDeleteFlag">Bỏ qua cờ xóa</param>
        /// <returns>Danh sách đối tượng entity</returns>
        public IList<TEntity> GetList<TEntity>(string localeCd, bool ignoreDeleteFlag) where TEntity : class
        {
            // Lấy entity
            var table = EntityManager.GetTable<TEntity>();
            // Lấy biểu thức (SelectList)
            var predicate = ExpressionSelectList<TEntity>(localeCd, ignoreDeleteFlag);
            // Kết quả trả về
            return table.Where(predicate).ToList();
        }

        /// <summary>
        /// Lấy danh sách đối tượng entity (TEntity)
        /// </summary>
        /// <typeparam name="TEntity">Đối tượng entity</typeparam>
        /// <param name="ignoreDeleteFlag">Bỏ qua cờ xóa</param>
        /// <returns>Danh sách đối tượng entity</returns>
        public IList<TEntity> GetList<TEntity>(bool ignoreDeleteFlag) where TEntity : class
        {
            return GetList<TEntity>(null, ignoreDeleteFlag);
        }

        #endregion

        #region Pirvate methods

        /// <summary>
        /// Lấy biểu thức (SelectSingle)
        /// </summary>
        /// <typeparam name="TEntity">Đối tượng entity</typeparam>
        /// <param name="keys">Giá trị các khóa chính</param>
        /// <param name="ignoreDeleteFlag">Bỏ qua cờ xóa</param>
        /// <returns>Biểu thức</returns>
        private Expression<Func<TEntity, bool>> ExpressionSelectSingle<TEntity>(string[] keys, bool ignoreDeleteFlag) where TEntity : class
        {
            // Lấy loại đối tượng entity
            var type = typeof(TEntity);
            // Tạo biểu thức entity
            var param = Expression.Parameter(type, "tbl");
            // Tạo biểu thức thuộc tính cho các field là khóa chính
            var meKeys = new List<MemberExpression>();
            var userMeta = EntityManager.Mapping.GetTable(type);
            var dataMembers = userMeta.RowType.PersistentDataMembers;
            // Diệt các thuộc tính khóa chính trong entity 
            foreach (var key in dataMembers.Where(o => o.IsPrimaryKey)) {
                meKeys.Add(Expression.Property(param, key.Name));
            }
            // Tạo biểu thức thuộc tính cho các field khác
            var meDeleteFlag = Expression.Property(param, DELETE_FLAG);
            // Tạo biểu thức hằng cho field là khóa chính
            var ceKeys = new List<ConstantExpression>();
            // Diệt giá trị các khóa chính
            foreach (var key in keys) {
                ceKeys.Add(Expression.Constant(key));
            }
            // Tạo biểu thức hằng cho các field khác
            var ceDeleteFlag = Expression.Constant(false, typeof(bool?));
            var ceIgnoreDeleteFlag = Expression.Constant(ignoreDeleteFlag);
            // Tạo biểu thức so sánh cho các field là khóa chính
            var beKeys = new List<BinaryExpression>();
            // Diệt danh sách biểu thức thuộc tính của khóa chính
            for (int i = 0; i < meKeys.Count; i++) {
                beKeys.Add(Expression.Equal(meKeys[i], ceKeys[i]));
            }
            // Tạo biểu thức so sánh cho các field khác
            var beDeleteFlag = Expression.Equal(meDeleteFlag, ceDeleteFlag);
            // Tạo biểu thức quan hệ cho các field là khóa chính
            BinaryExpression exKeys = null;
            if (beKeys.Count > 0) {
                exKeys = GetExpressionKey(beKeys, beKeys.Count - 1);
            }
            // Tạo biểu thức quan hệ cho các field field khác
            var exDeleteFlag = Expression.Or(beDeleteFlag, ceIgnoreDeleteFlag);
            // Tạo biểu thức lambda
            BinaryExpression ex = null;
            if (exKeys != null) {
                ex = Expression.And(exKeys, exDeleteFlag);
            } else {
                ex = exDeleteFlag;
            }
            // Kết quả trả về
            return Expression.Lambda<Func<TEntity, bool>>(ex, param);
        }

        /// <summary>
        /// Lấy biểu thức quan hệ cho khóa chính expression key
        /// </summary>
        /// <param name="beKeys">Danh sách biểu thức so sánh của khóa chính</param>
        /// <param name="index">Chỉ số hiện tại</param>
        /// <returns>Biểu thức quan hệ</returns>
        private BinaryExpression GetExpressionKey(List<BinaryExpression> beKeys, int index)
        {
            // Trường hợp chỉ số là 0
            if (index == 0) {
                return beKeys[0];
            }
            // Kết quả trả về
            return Expression.And(beKeys[index], GetExpressionKey(beKeys, index - 1));
        }

        /// <summary>
        /// Lấy biểu thức (SelectList)
        /// </summary>
        /// <typeparam name="TEntity">Đối tượng entity</typeparam>
        /// <param name="localeCd">Mã ngôn ngữ</param>
        /// <param name="ignoreDeleteFlag">Bỏ qua cờ xóa</param>
        /// <returns>Biểu thức</returns>
        private Expression<Func<TEntity, bool>> ExpressionSelectList<TEntity>(string localeCd, bool ignoreDeleteFlag) where TEntity : class
        {
            // Lấy loại đối tượng entity
            var type = typeof(TEntity);
            // Tạo biểu thức entity
            var peTbl = Expression.Parameter(type, "tbl");
            // Tạo biểu thức thuộc tính
            var meLocaleCd = Expression.Property(peTbl, LOCALE_CD);
            var meDeleteFlag = Expression.Property(peTbl, DELETE_FLAG);
            // Tạo biểu thức hằng
            var ceLocaleCd = Expression.Constant(localeCd);
            var ceDeleteFlag = Expression.Constant(false, typeof(bool?));
            var ceIgnoreDeleteFlag = Expression.Constant(ignoreDeleteFlag);
            // Tạo biểu thức so sánh
            var beLocaleCd = Expression.Equal(meLocaleCd, ceLocaleCd);
            var beDeleteFlag = Expression.Equal(meDeleteFlag, ceDeleteFlag);
            // Tạo biểu thức quan hệ
            var exDeleteFlag = Expression.Or(beDeleteFlag, ceIgnoreDeleteFlag);
            // Tạo biểu thức lambda
            BinaryExpression ex = null;
            if (localeCd != null) {
                ex = Expression.And(beLocaleCd, exDeleteFlag);
            } else {
                ex = exDeleteFlag;
            }
            // Kết quả trả về
            return Expression.Lambda<Func<TEntity, bool>>(ex, peTbl);
        }

        #endregion
    }
}